package msc.ai.crypto.jade;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;
import msc.ai.crypto.jade.security.KDC;
import msc.ai.crypto.jade.security.keymanager.AgentPublicKeyDetails;

public class ShopperAgent extends Agent
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -4181904484336886785L;

	private ShopperGui shopperGui;
	
	private ArrayList<AID> storeList = new ArrayList<AID>();
	private ArrayList<AID> kyMgrList = new ArrayList<AID>();
	
	private AgentPublicKeyDetails myKeyMgrKeys;
	
	protected void setup() 
	{

		shopperGui = new ShopperGui(this);
		shopperGui.showGui();

		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID());
		ServiceDescription sd = new ServiceDescription();
		sd.setType("online-buying");
		sd.setName("online-store-buying");
		dfd.addServices(sd);
		try 
		{
			DFService.register(this, dfd);
		}
		catch (FIPAException fe) 
		{
			fe.printStackTrace();
		}	
		
		addBehaviour(new TickerBehaviour(this, 30000) 
		{
		      protected void onTick() 
		      {
		        
		        DFAgentDescription template = new DFAgentDescription();
		        ServiceDescription sd = new ServiceDescription();
		        sd.setType("online-store");
		        template.addServices(sd);
		        try 
		        {
		          DFAgentDescription[] result = DFService.search(myAgent, template);
		          storeList.clear();
		          for (int i = 0; i < result.length; ++i) 
		          {
		        	  storeList.add(result[i].getName());  
		          }
		        }
		        catch (FIPAException fe) 
		        {
		          fe.printStackTrace();
		        }
		      }
	    } );
		  
		
		// Find Key Managers
		addBehaviour(new TickerBehaviour(this, 10000) 
			{
				protected void onTick() 
				{	
						DFAgentDescription template = new DFAgentDescription();
						ServiceDescription sd = new ServiceDescription();
						sd.setType("key-manager");
						template.addServices(sd);
						try 
						{
							DFAgentDescription[] result = DFService.search(myAgent, template);
							boolean isMyKeyMgrAlive = false;
							
							kyMgrList.clear();
							for (int i = 0; i < result.length; ++i) 
							{
								kyMgrList.add(result[i].getName());
								
								if (myKeyMgrKeys != null && (result[i].getName().equals(myKeyMgrKeys.getKeyMgrAID())))
								{
									isMyKeyMgrAlive = true;
								}
								
							}
							
							// need to register ?
							if (myKeyMgrKeys == null)
							{
								// Discard old shared keys with old key manager
								myAgent.clearSharedKeyStore();
								myAgent.addBehaviour(new KeyManagerRegisterBehavior());
							}
							else if (isMyKeyMgrAlive == false)
							{
								// Old Key Manager is dead. Register with a new key manger
								// Discard old shared keys with old key manager
								myAgent.clearSharedKeyStore();							
								myAgent.addBehaviour(new KeyManagerRegisterBehavior());
							}			
							
						}
						catch (FIPAException fe) 
						{
							fe.printStackTrace();
						}
					
				}
			} 
		);
		
		System.out.println(getAID().getLocalName() + ": Shopper Started");
				
	}
	
	protected void takeDown() 
	{
		
		try 
		{
			DFService.deregister(this);
		}
		catch (FIPAException fe) 
		{
			fe.printStackTrace();
		}
		
		this.shopperGui.dispose();
		
		System.out.println("Shopper-agent "+getAID().getName()+" terminating.");
	}
	
	public void buyItem(BuyingItem item)
	{
		if (!item.getItemSearching().isEmpty() && item.getItemQtyRequired() > 0 && item.getMaxItemPrice() > 0 &&
				!item.getCreditCardNo().isEmpty() && !item.getCreditCVVNo().isEmpty() && !item.getCreditCardType().isEmpty())
		{
			// everyting set, add the behavior
			System.out.println(getAID().getLocalName() + " : Going to buy " + item.getItemSearching() 
					+ ", Qty : " + item.getItemQtyRequired() + ", MaxPrice " + item.getMaxItemPrice());
			addBehaviour(new ItemRequestBehavior(this, item));
			
			
		}
		else
		{
			System.out.println(getAID().getLocalName() + ": Cannot Buy. Invalid or incomplete buying information.!!!");
		}
	}
	
	// FIXME : Should this be a ticker behaviour?
	//			Simple behaviour with steps????
	private class ItemRequestBehavior extends TickerBehaviour 
	{
	    private BuyingItem itemBuying;
	    private Agent agt;
	    
	    private ItemRequestBehavior(Agent agt, BuyingItem item) 
	    {
	      super(agt, 15000);
	      this.agt = agt;
	      this.itemBuying = item;
	    }

	    public void onTick() 
	    {	     
	        myAgent.addBehaviour(new ItemPurchaseBehavior(agt,this,itemBuying));
	      
	    }
	    
	    
	}
	
	private class ItemPurchaseBehavior extends Behaviour
	{
		
		/**
		 * 
		 */
		private static final long serialVersionUID = -3229690592710335512L;

		private BuyingItem buyingItem;
		private ItemRequestBehavior reqBehavior;
		
		private AID bestSeller;
		private SalesItem bestSalesQuote;
		private double bestPrice;  
		private int bestQty;
		private int step = 0;
		private int repliesCnt = 0;
		private MessageTemplate mt; // The template to receive replies
		
		
		public ItemPurchaseBehavior(Agent agt,ItemRequestBehavior reqBehavior, BuyingItem item)
		{
			super(agt);
			this.buyingItem = item;
			this.reqBehavior = reqBehavior;
		} 
		@Override
		public void action()
		{
			switch (this.step)
			{
				case 0:
					ACLMessage cfp = new ACLMessage(ACLMessage.CFP);
					for (int i = 0; i < storeList.size(); ++i) 
					{
						cfp.addReceiver((AID)storeList.get(i));
					}
					cfp.setContent(buyingItem.getItemSearching());
					cfp.setConversationId("online-store-trade");
					cfp.setReplyWith("cfp"+System.currentTimeMillis()); // Unique value
					myAgent.send(cfp, KDC.getKey()); // this is unsecure
			          
					mt = MessageTemplate.and(
							MessageTemplate.MatchConversationId("online-store-trade"),
							MessageTemplate.MatchInReplyTo(cfp.getReplyWith())
						);
					step = 1;
					break;
					
					
				case 1:

					ACLMessage reply = myAgent.receive(mt); // this is secure
					if (reply != null) 
					{
						if (reply.getPerformative() == ACLMessage.PROPOSE) 
						{ 
							
							try
							{	
								SalesItem sItem = (SalesItem)reply.getContentObject();
							
//								String msgParts[] = replyMsg.split(";");
								double itemPrice = sItem.getItemPrice();
								int itemQty = sItem.getItemQty();
								
								System.out.println(getAID().getLocalName() + ": Reply from " + reply.getSender().getName() + ": " + sItem.getItemName());
								
								if ( (bestSeller == null && itemPrice <= buyingItem.getMaxItemPrice() )|| ( (itemPrice < bestPrice && itemPrice < buyingItem.getMaxItemPrice() )&& 
										(itemQty >= buyingItem.getItemQtyRequired() || itemQty >= bestQty))) 
								{
									bestSalesQuote = sItem;
									bestPrice = itemPrice;
									bestQty = itemQty;
									bestSeller = reply.getSender();
								}
							} 
							catch (UnreadableException e)
							{
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}
						repliesCnt++;
						if (repliesCnt >= storeList.size())
						{	
							step = 2; 
						}
					}
					else 
					{
						block();
					}
					break;
					
				case 2:
					if (bestSeller != null) 
					{
			            // Send the purchase order to the seller that provided the best offer
						// EDIT : Need to setup a secure communication first.
						// Get the the session key from key store after that
						
						try
						{
							System.out.println(myAgent.getLocalName() + ": Sending purchase order to " + bestSeller.getLocalName());
							System.out.println(myAgent.getLocalName() + ": PO Details - Price : " + bestSalesQuote.getItemPrice() + ", Qty : " + bestSalesQuote.getItemQty());
				            ACLMessage order = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
				            order.addReceiver(bestSeller);			            
				            order.setContentObject(buyingItem);			            
				            order.setConversationId("online-store-trade");
				            order.setReplyWith("order"+System.currentTimeMillis());
				            myAgent.send(order, KDC.getKey());
				            // Prepare the template to get the purchase order reply
				            mt = MessageTemplate.and(
				              MessageTemplate.MatchConversationId("online-store-trade"),
				              MessageTemplate.MatchInReplyTo(order.getReplyWith())
				            );
				            
				            step = 3;
						}
						catch (Exception e) 
						{
							// TODO: handle exception
							e.printStackTrace();
						}
			          }
			          else 
			          {
			            // no proposal
			        	System.out.println(myAgent.getLocalName()+ ": No seller found");
			            step = 4;
			          }
			          break;
			          
				case 3:
					
					// Receive the purchase order reply
					reply = myAgent.receive(mt, KDC.getKey());
					if (reply != null) 
					{
						// Purchase order reply received
						if (reply.getPerformative() == ACLMessage.INFORM) 
						{
							System.out.println(myAgent.getLocalName() + ": Item successfully purchased from agent " + reply.getSender().getName()
									+ "\n----------------------------------------------------\n");							
							//myAgent.doDelete();
							reqBehavior.stop();
						}
						else 
						{
							System.out.println(myAgent.getLocalName() + ": Buying Attempt failed: " + reply.getContent() 
									+ "\n----------------------------------------------------\n");
							reqBehavior.stop();
						}

						step = 4;
					}
					else {
						block();
					}
					break;
				default:
					break;
			}
			
		}
		@Override
		public boolean done()
		{
			if (step == 4 || (step == 2 && bestSeller == null))	return true;
			else return false;
		}
		
		
	} // End ItemPurchaseBehavior
	
	
	
	
	private class KeyManagerRegisterBehavior extends Behaviour
	{

	
				
		/**
		 * 
		 */
		private static final long serialVersionUID = -3100102905728885003L;
		
		
		private int registerStep = 0;		
		private MessageTemplate mt;
		
		@Override
		public void action()
		{
			switch (registerStep)
			{
				case 0:	
					// Register with Key Manager
					System.out.println(getAID().getLocalName() + ": Key Manager Registration started");
					ACLMessage req = new ACLMessage(ACLMessage.REQUEST);
					for (int i = 0; i < kyMgrList.size(); ++i) 
					{
						//TODO: Select only one???
						req.addReceiver((AID)kyMgrList.get(i));
					}
					
					req.setConversationId("keymgr_register");
					req.setReplyWith("register_"+System.currentTimeMillis()); // Unique value
//					req.setSecureMsg(false);
					myAgent.send(req); // send unsecured message
//					System.out.println(getAID().getLocalName() + ": Waiting for reply to : " + req.getReplyWith());
			          
					mt = MessageTemplate.and(
							MessageTemplate.MatchPerformative(ACLMessage.INFORM),
							MessageTemplate.and(
									MessageTemplate.MatchConversationId("keymgr_register_done"),
									MessageTemplate.MatchInReplyTo(req.getReplyWith())
							)
						);
					
					
					registerStep = 1;
					break;
					
				case 1:
					// Receive Key Details
					
					ACLMessage keyMgrReply = myAgent.receive(mt); // this is unsecure
					if (keyMgrReply != null) 
					{
						System.out.println(getAID().getLocalName() + ": Key Manager Regisration Done"); 
						try
						{
							AgentPublicKeyDetails pkDetails = (AgentPublicKeyDetails) keyMgrReply.getContentObject();							
							myKeyMgrKeys = pkDetails;
							
							System.out.println(getAID().getLocalName() + ": Key Manager " + myKeyMgrKeys.getKeyMgrAID().getLocalName());
							registerStep = 2;
							
						} catch (UnreadableException e)
						{
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						
						
					}
					else
					{
						//TODO block(); ?

					}
					
					break;
				default:
					break;
			}
			
		}

		@Override
		public boolean done()
		{
			 
			if (registerStep == 2) 
			{
				return true;
			}
			return false;
		}
		
		
	} // End of KeyManagerRegisterBehavior	
	
	private class AcceptSharedKeyBehavior extends Behaviour
	{
		private int sharedKeyStep = 0;

		@Override
		public void action()
		{
			
			
		}

		@Override
		public boolean done()
		{
			// TODO Auto-generated method stub
			return false;
		}
		
	}
	

}
